/*******************************************************************************
 * Copyright (c) 2011 squid.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     squid - initial API and implementation
 ******************************************************************************/
package com.google.code.cubeirc.scripting;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.HashMap;
import java.util.concurrent.ConcurrentSkipListSet;

import javax.script.Bindings;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import lombok.Getter;
import lombok.Setter;
import lombok.Synchronized;

import org.apache.log4j.Level;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Shell;
import org.pircbotx.hooks.Event;

import sun.org.mozilla.javascript.internal.NativeArray;

import com.google.code.cubeirc.connection.CubeIRC;
import com.google.code.cubeirc.debug.DebuggerQueue;
import com.google.code.cubeirc.dialogs.AlertBox;
import com.google.code.cubeirc.queue.MessageQueue;
import com.google.code.cubeirc.queue.MessageQueueEnum;

public final class Script implements Comparable<Script> {

	public static final int JAVASCRIPT = 1;
	public static final int RUBY = 2;

	@Getter
	private int scriptType=0;

	@Getter @Setter
	private boolean inUse = true;
	@Getter
	private String script;
	@Getter
	private File reference;
	@Getter
	private ConcurrentSkipListSet<String> functions = new ConcurrentSkipListSet<String>();
	@Getter
	private ConcurrentSkipListSet<String> descriptFunctions = new ConcurrentSkipListSet<String>();

	/**
	 * Gets the func_help.
	 *
	 * @return the func_help
	 */
	@Getter
	private HashMap<String, String> func_help = new HashMap<String, String>();


	private static final String nl = System.getProperty("line.separator");
	private ScriptEngineManager manager = new ScriptEngineManager();
	private ScriptEngine jsEngine = manager.getEngineByName("JavaScript");
	private ScriptEngine rbEngine = manager.getEngineByName("jruby");
	private Invocable jsInvocable = (Invocable) jsEngine;
	private Invocable rbInvocable = (Invocable) rbEngine;	
	private Bindings bindings = jsEngine.getBindings(ScriptContext.ENGINE_SCOPE);

	private String rbBase = "require \"java\""+nl;

	private String jsBase = "importClass(org.eclipse.swt.SWT);" +nl+
			//"importClass(Packages.hexapixel.notifier.NotificationType);" +nl+
			"importClass(Packages.org.pircbotx.Colors);" +nl+
			"importClass(java.lang.Thread);" +nl+
			"importPackage(org.pircbotx);" +nl+
			"importPackage(java.util);" +nl+
			"importPackage(java.lang);" +nl;

	public Script(File f) {
		this.reference = f;

		String s = reference.getName().substring(reference.getName().indexOf('.'));

		if(s.equals(".js")){
			scriptType=JAVASCRIPT;
		} else if(s.equals(".rb")){
			scriptType=RUBY;
		}

		reset();
	}

	public void reset() {
		readScript();

		initBindings();

		initialize();
	}

	private void initialize() {
		try {
			switch(scriptType){
			case JAVASCRIPT:
				jsEngine.eval(jsBase+script);
				break;
			case RUBY:
				rbEngine.eval(rbBase+"puts 'hello world'");
				break;
			}
		} catch (ScriptException e) {


			DebuggerQueue.addDebug(this, Level.FATAL, "Script: %s Error %s line: %s",reference.getName(), e.getMessage(), e.getLineNumber());

			new AlertBox(new Shell(),"Script Read Error", reference.getName()+" has an error. Due to error reporting methods, I can not help you narrow down the issue.", SWT.ICON_ERROR);


		}
	}

	private void initBindings() {
		bindings.put("base", new BaseCommands());
		bindings.put("util", new UtilsCommands());
	}

	public void readScript() {

		//reset the functions list
		functions.clear();
		descriptFunctions.clear();

		//re-parse the script
		StringBuffer contents = new StringBuffer();

		BufferedReader reader = null;
		if(!reference.exists()) return;

		try {
			reader = new BufferedReader(new FileReader(reference));
			String text = null;
			while((text = reader.readLine())!=null){
				contents.append(text).append(System.getProperty("line.separator"));
				parseFunction(text);
			}
			reader.close();
		} catch (Exception e) {
			DebuggerQueue.addDebug(this, Level.FATAL, "Script: %s Error %s",reference.getName(), e.getMessage());


		}

		script = contents.toString();
	}

	//check line-by-line for a function name
	private void parseFunction(String text) {
		if(text.toLowerCase().contains("meta")) {
			parseMeta(text);
		}
		if(text.toLowerCase().startsWith("//help")) {
			parseHelp(text);
		}
		switch(scriptType){
		case JAVASCRIPT:
			if(!text.contains("function ")) return;
			String elFunction = text.substring(9).split("[{]")[0].trim();
			descriptFunctions.add(elFunction);
			String[] array = text.replaceAll("[(]", " ").split(" ");
			//function, onFunctionName, event), {
			functions.add(array[1].trim());
			break;
		case RUBY:
			if(!text.contains("def ")) return;
			String rbFunction = text.substring(4);
			descriptFunctions.add(rbFunction);
			String[] rbarray = text.replaceAll("[(]", " ").split(" ");
			//def function(var) 
			functions.add(rbarray[1].trim());
			break;
		}
	}

	private void parseMeta(String text){
		//META<inuse=false>
		String[] cleanMeta = text.replaceAll("[<>]", " ").split(" ")[1].split("=");
		if(cleanMeta[0].equals("inuse")){
			this.inUse = Boolean.parseBoolean(cleanMeta[1]);
		}
	}

	//event invocation
	public void invoke(String function, Event<CubeIRC> e){
		if(!inUse)return;
		try {
			switch(scriptType){
			case JAVASCRIPT:
				jsInvocable.invokeFunction(function, e);
				break;
			case RUBY:
				rbInvocable.invokeFunction(function, e);
				break;
			}
		} catch (NoSuchMethodException e1) {
			DebuggerQueue.addDebug(this, Level.FATAL, "Script: %s Error %s",reference.getName(), e1.getMessage());

		} catch (ScriptException e1) {
			DebuggerQueue.addDebug(this, Level.FATAL, "Script: %s Error %s line: %s",reference.getName(), e1.getMessage(), e1.getLineNumber());

		}
	}

	//open-ended invocation
	public void invoke(String command, Object... args) { 
		if(!inUse)return;
		try {
			switch(scriptType){
			case JAVASCRIPT:
				jsInvocable.invokeFunction(command, args);
				break;
			case RUBY:
				rbInvocable.invokeFunction(command, args);
				break;
			}
		} catch (NoSuchMethodException e1) {
			DebuggerQueue.addDebug(this, Level.FATAL, "Script: %s Error %s",reference.getName(), e1.getMessage());
			MessageQueue.addQueue(MessageQueueEnum.CONSOLE, String.format("Script: %s Error %s",reference.getName(), e1.getMessage()));

		} catch (ScriptException e1) {
			DebuggerQueue.addDebug(this, Level.FATAL, "Script: %s Error %s line: %s",reference.getName(), e1.getMessage(), e1.getLineNumber());
			MessageQueue.addQueue(MessageQueueEnum.CONSOLE, String.format("Script: %s Error %s",reference.getName(), e1.getMessage()));
		}
	}

	public Object[] invoke(String command) {
		Object[] rv = null;
		try {
			switch(scriptType){
			case JAVASCRIPT:
				NativeArray arr = (NativeArray) jsInvocable.invokeFunction(command);
				rv = new Object[(int)arr.getLength()];

				//get as object
				for(Object o : arr.getIds()) {
					int index = (Integer) o;
					rv[index] = arr.get(index, null);
				}
				break;
			case RUBY:
				rv = (Object[]) rbInvocable.invokeFunction(command);
			}
		} catch (NoSuchMethodException e) {
			DebuggerQueue.addDebug(this, Level.FATAL, "Script: %s Error %s",reference.getName(), e.getMessage());
			MessageQueue.addQueue(MessageQueueEnum.CONSOLE, String.format("Script: %s Error %s",reference.getName(), e.getMessage()));


		} catch (ScriptException e1) {
			DebuggerQueue.addDebug(this, Level.FATAL, "Script: %s Error %s line: %s",reference.getName(), e1.getMessage(), e1.getLineNumber());
			MessageQueue.addQueue(MessageQueueEnum.CONSOLE, String.format("Script: %s Error %s",reference.getName(), e1.getMessage()));

		}
		return rv;
	}

	@Override
	public int compareTo(Script o) {
		return reference.getName().compareTo(o.reference.getName());
	}

	/**
	 * Parses the help.
	 *
	 * @param text the text
	 */
	@Synchronized
	private void parseHelp(final String text)
	{
		//HELP<functionname=blab>
		String[] cleanMeta = text.replaceAll("[<>]", " ").replace("//HELP", "").split("=");
		this.func_help.put(cleanMeta[0].trim(), cleanMeta[1]);

	}
}
